Skip to content

Conversation

@esezen
Copy link
Contributor

@esezen esezen commented Jan 28, 2026

Pull Request Checklist

Before you submit a pull request, please make sure you have to following:

  • I have added or updated TypeScript types for my changes, ensuring they are compatible with the existing codebase.
  • I have added JSDoc comments to my TypeScript definitions for improved documentation.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added any necessary documentation (if appropriate).
  • [ x I have made sure my PR is up-to-date with the main branch.

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no API changes)
  • Documentation content changes
  • TypeScript type definitions update
  • Other... Please describe:

Clone of #192

Copilot AI review requested due to automatic review settings January 28, 2026 12:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds functionality to allow facets and facet options to be hidden from the filter UI through three methods: custom functions, metadata flags, or custom field getters. This enables conditional rendering of filters on a per-page basis (e.g., showing a facet on search results but hiding it on category pages).

Changes:

  • Added isHiddenFilterFn and isHiddenFilterOptionFn props to filter components for custom hiding logic
  • Introduced getIsHiddenFilterField and getIsHiddenFilterOptionField field getters to support metadata-based hiding via cio_plp_hidden flag
  • Enhanced useOptionsList hook with recursive filtering capability for nested options in hierarchical structures

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/utils/itemFieldGetters.ts Added field getter functions for detecting hidden filters and filter options
src/types.ts Updated type definitions to support new field getters and changed data type from object to Record<string, any>
src/stories/components/Filters/Filters.stories.tsx Added story examples demonstrating the three methods for hiding filters
src/stories/components/Filters/Code Examples.mdx Added comprehensive documentation with code examples for the hiding functionality
src/hooks/useOptionsList.ts Added recursive filtering support for nested options via nestedOptionsKey parameter
src/hooks/useGroups.ts Enabled recursive filtering for group children by passing nestedOptionsKey: 'children'
src/hooks/useFilter.ts Implemented facet filtering logic using both custom function and metadata-based approaches
src/components/Filters/UseFilterOptionsList.tsx Added support for hiding individual filter options using custom functions and metadata
src/components/Filters/Filters.tsx Passed through isHiddenFilterOptionFn prop to child components
src/components/Filters/FilterGroup.tsx Passed through isHiddenFilterOptionFn prop to FilterOptionsList
spec/hooks/useGroups/useGroups.test.js Added tests for recursive filtering of nested group children
spec/hooks/useFilter/useFilter.test.js Added comprehensive tests for facet hiding functionality
spec/components/Filters/Filters.test.jsx Added integration tests for filter and filter option hiding

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

displayName: string;
value: string;
data: object;
data: Record<string, any>;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any bypasses TypeScript's type safety. Consider using unknown instead, which provides type safety while still allowing flexibility: Record<string, unknown>.

Copilot uses AI. Check for mistakes.
export interface PlpHierarchicalFacetOption extends PlpFacetOption {
options: Array<PlpHierarchicalFacetOption>;
data: object & { parentValue: string | null };
data: Record<string, any> & { parentValue: string | null };
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any bypasses TypeScript's type safety. Consider using unknown instead: Record<string, unknown> & { parentValue: string | null }.

Suggested change
data: Record<string, any> & { parentValue: string | null };
data: Record<string, unknown> & { parentValue: string | null };

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +42
const nestedOptions = (option as Record<string, any>)[nestedKey];
if (Array.isArray(nestedOptions) && nestedOptions.length > 0) {
return {
...option,
[nestedKey]: filterOptionsRecursively(nestedOptions, isHiddenFn, nestedKey),
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using any bypasses TypeScript's type safety. Consider using unknown instead and adding proper type guards: (option as Record<string, unknown>)[nestedKey].

Suggested change
const nestedOptions = (option as Record<string, any>)[nestedKey];
if (Array.isArray(nestedOptions) && nestedOptions.length > 0) {
return {
...option,
[nestedKey]: filterOptionsRecursively(nestedOptions, isHiddenFn, nestedKey),
const nestedOptions = (option as Record<string, unknown>)[nestedKey];
if (Array.isArray(nestedOptions) && nestedOptions.length > 0) {
return {
...option,
[nestedKey]: filterOptionsRecursively(nestedOptions as Array<T>, isHiddenFn, nestedKey),

Copilot uses AI. Check for mistakes.
@constructor-claude-bedrock
Copy link

Code Review Results

✅ Strengths

Well-structured feature with comprehensive test coverage, clear documentation, and thoughtful API design that supports multiple hiding strategies (custom functions, metadata field, and custom field getters).

🚨 Critical Issues

[File: src/hooks/useOptionsList.ts Line: L38]
The recursive filtering function uses unsafe type casting (option as Record<string, any>)[nestedKey] which bypasses TypeScript's type safety. This could lead to runtime errors if the nested property doesn't exist or isn't an array.

Recommendation: Use proper type guards or optional chaining:

function filterOptionsRecursively<T>(
  options: Array<T>,
  isHiddenFn: (option: T) => boolean,
  nestedKey: string,
): Array<T> {
  return options
    .filter((option) => !isHiddenFn(option))
    .map((option) => {
      const nestedOptions = (option as any)?.[nestedKey];
      if (Array.isArray(nestedOptions) && nestedOptions.length > 0) {
        return {
          ...option,
          [nestedKey]: filterOptionsRecursively(nestedOptions, isHiddenFn, nestedKey),
        };
      }
      return option;
    });
}

[File: src/components/Filters/UseFilterOptionsList.tsx Line: L58]
The effect that builds selectedOptionsMap from facet.options doesn't account for hidden options. If an option is hidden but was previously selected (e.g., from URL state), it will be included in the selected map but won't be visible to users. This creates inconsistent state.

Recommendation: Filter out hidden options before building the selected map:

useEffect(() => {
  const newSelectedOptionsMap = {};
  const visibleOptions = facet.options.filter((option) => !isHiddenOptionFn(option));
  visibleOptions.forEach((option) => {
    newSelectedOptionsMap[option.value] = option.status === 'selected';
  });
  setSelectedOptionMap(newSelectedOptionsMap);
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [facet, isHiddenOptionFn]);

⚠️ Important Issues

[File: src/hooks/useOptionsList.ts Line: L62]
The useMemo dependency array includes nestedOptionsKey which is typically a constant string. While not incorrect, this could cause unnecessary re-filtering. Consider if this dependency is truly needed.

[File: src/hooks/useFilter.ts Line: L45-51]
[File: src/hooks/useGroups.ts Line: L47-53]
[File: src/components/Filters/UseFilterOptionsList.tsx Line: L29-36]
The pattern for combining isHiddenFn and getIsHiddenField is duplicated across three files. This logic should be extracted into a reusable utility function to improve maintainability.

Recommendation: Create a utility function:

// src/utils/filterHelpers.ts
export function createIsHiddenFn<T>(
  customFn?: (item: T) => boolean,
  metadataFn?: (item: T) => boolean | undefined,
): (item: T) => boolean {
  return (item: T) =>
    (typeof customFn === 'function' && customFn(item)) ||
    (typeof metadataFn === 'function' && metadataFn(item)) ||
    false;
}

[File: src/stories/components/Filters/Code Examples.mdx Line: L91]
Documentation states this feature is "intended for conditional rendering per page" but doesn't provide clear guidance on when NOT to use it. The note is good but could be more prominent (e.g., a warning callout).

💡 Suggestions

[File: spec/components/Filters/Filters.test.jsx Line: L757-773]
Test comment says "lowercase 'color'" which is implementation detail leakage. Consider removing the comment or making it more descriptive about why the facet name matters (e.g., "matching the facet name from mockTransformedFacets").

[File: src/hooks/useOptionsList.ts Line: L30-47]
The recursive filtering function would benefit from JSDoc comments explaining the algorithm and edge cases (e.g., what happens with circular references, deeply nested structures).

[File: src/utils/itemFieldGetters.ts Line: L56-66]
Return type annotations are inconsistent - some functions explicitly return boolean | undefined in the type definition but the implementations just use optional chaining. Consider adding explicit return type annotations to the function implementations for clarity:

export function getIsHiddenFilterField(facet: PlpFacet): boolean | undefined {
  return facet?.data?.cio_plp_hidden;
}

[File: spec/hooks/useFilter/useFilter.test.js Line: L299-337]
The test "Should use custom getIsHiddenFilterField from itemFieldGetters" creates a custom wrapper inline. Consider extracting this to a test utility function since this pattern might be useful for other tests.

[General]
Consider adding performance tests or benchmarks for the recursive filtering logic, especially with deeply nested hierarchies (e.g., 5+ levels deep with hundreds of options). The current implementation creates new objects at every level which could be expensive for large datasets.

Overall Assessment: ⚠️ Needs Work

The feature is well-designed and comprehensively tested, but has two critical issues that should be addressed:

  1. Type safety issue in recursive filtering
  2. State management issue with hidden selected options

These issues could cause runtime errors or UX inconsistencies in production. Once resolved, this will be a solid addition to the codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants